package fsnotifyalter

import (
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"gitee.com/boychengd/goutils"
	mapset "github.com/deckarep/golang-set"
	"github.com/fsnotify/fsnotify"
	"go.uber.org/zap"
)

type NotifyMessage struct {
	fileName string
	events   mapset.Set
}

type Notify struct {
	LogFilename  string // 日志文件的位置
	LogMaxSize   int    // 在进行切割之前，日志文件的最大大小（以MB为单位）
	LogMaxBackup int
	LogMaxAge    int      // 保留旧文件的最大个数
	Nets         []string // 需要获取指定网卡名称，只需要简写前2位字符
	NotifyFiles  []string // 需要监控文件或目录
	Alters       []Alter  // 告警方式
	alterData    *sync.Map
}

// 初始化Notify
func NewNotify(logFilename string, nets, notifyfiles []string, alter ...Alter) *Notify {
	return &Notify{
		LogFilename:  logFilename,
		LogMaxSize:   100,
		LogMaxBackup: 5,
		LogMaxAge:    30,
		Nets:         nets,
		NotifyFiles:  notifyfiles,
		Alters:       alter,
		alterData:    &sync.Map{},
	}
}

func (n Notify) NotifyFile() {
	n.initLogger()
	n.tickSendMsg()
	// Create new watcher.
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	// Start listening for events.
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}

				logger.Info("EVENT事件触发", zap.String("message", event.String()))
				// 判断当前触发变更文件路径是否在n.alterData中，存在就更新NotifyMessage的events，不在则新增NotifyMessage
				fname := event.Name
				ename := event.Op.String()
				if v, ok := n.alterData.Load(fname); ok {
					nm := v.(*NotifyMessage)
					if !nm.events.Contains(ename) {
						nm.events.Add(ename)
					}
				} else {
					nm := &NotifyMessage{
						fileName: fname,
					}
					nm.events = mapset.NewSet()
					nm.events.Add(ename)
					n.alterData.Store(fname, nm)
				}
				// logger.Debug("告警信息发送事件", zap.String("message", fmt.Sprintf("%v", n.alterData)))
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("error:", err)
			}
		}
	}()

	// Add a path.
	for _, f := range n.NotifyFiles {
		ok, err := goutils.PathIsExist(f)
		if err != nil {
			logger.Panic("文件或目录异常错误", zap.String("path", f))
		}
		if ok {
			err = watcher.Add(f)
			if err != nil {
				logger.Panic("文件或目录监控失败", zap.String("message", err.Error()), zap.String("path", f))
			}
		} else {
			logger.Warn("文件或目录不存在", zap.String("path", f))
		}

	}
	logger.Info("Fsnotify 启动成功")
	// Block main goroutine forever.
	<-make(chan struct{})
}

func (n Notify) tickSendMsg() {
	ticker := time.NewTicker(time.Second * 10)
	ipv4s, _ := goutils.GetIPV4s(n.Nets)
	go func() {
		for range ticker.C {
			n.alterData.Range(func(key, value interface{}) bool {
				nm := value.(*NotifyMessage)
				title := "[文件|目录] 篡改告警"
				msg := fmt.Sprintf("## %s\n", title) +
					"---  \n" +
					fmt.Sprintf("> **文件**: %s  \n", nm.fileName) +
					fmt.Sprintf("> **状态**: %s  \n", goutils.MapsetJoin(nm.events, " | ")) +
					fmt.Sprintf("> **IP地址**: %s  \n", "\n> - "+strings.Join(ipv4s, "\n> - "))
				for _, a := range n.Alters {
					a.send(title, msg)
				}
				n.alterData.Delete(key)
				return true
			})

		}
	}()
}
